Перейти к основному содержимому

5.16. Типы данных

Разработчику Архитектору

Типы данных

Язык программирования Pascal, разработанный Никлаусом Виртом в начале 1970-х годов, задумывался как инструмент для обучения структурному программированию и строгой типизации. Одним из ключевых принципов языка стала явная и предсказуемая система типов, которая помогает программисту точно определять, какие значения может принимать переменная, какие операции допустимы над ней и как она хранится в памяти. Эта система типов делится на две большие категории: простые (или скалярные) и составные (или структурированные). Каждый тип в Pascal имеет чётко определённое множество значений и набор операций, применимых к этим значениям.

Простые типы данных

Простые типы данных представляют собой неделимые единицы информации. Они не содержат внутренней структуры и не могут быть разложены на более мелкие компоненты с точки зрения языка. В Pascal к простым типам относятся целочисленные, вещественные, логические и символьные типы. Эти типы формируют основу для всех вычислений и логических решений в программах.

Целочисленный тип (integer)

Целочисленный тип используется для представления чисел без дробной части. В классическом Pascal стандартный тип integer обычно соответствует 16-битному знаковому целому числу, что даёт диапазон от -32768 до 32767. Современные реализации Pascal, такие как Free Pascal или Delphi, расширяют этот диапазон и предоставляют дополнительные целочисленные типы: shortint, byte, word, longint, int64 и другие. Каждый из этих типов имеет своё собственное количество байтов и диапазон допустимых значений.

Над целыми числами определены арифметические операции: сложение, вычитание, умножение, целочисленное деление (div) и получение остатка от деления (mod). Также доступны операции сравнения: равно, не равно, больше, меньше и их комбинации. Целочисленные типы часто используются для организации циклов, подсчёта элементов, индексации массивов и любых задач, где требуется точность без округления.

Вещественный тип (real)

Вещественный тип предназначен для хранения чисел с дробной частью. В оригинальном Pascal тип real обычно занимал 6 байтов и обеспечивал ограниченную точность, достаточную для учебных задач. В современных реализациях чаще используются стандартные IEEE 754 форматы: single (32 бита), double (64 бита), extended (80 бит). Эти типы позволяют работать с очень большими или очень малыми числами, но вводят особенности, связанные с плавающей запятой: невозможность точного представления некоторых десятичных дробей, накопление ошибок округления при многократных вычислениях.

Операции над вещественными числами включают сложение, вычитание, умножение, деление, а также богатый набор математических функций: sin, cos, sqrt, ln, exp и другие. При сравнении вещественных чисел рекомендуется использовать допуск (например, проверять, что разность меньше некоторого малого значения), поскольку прямое равенство может не сработать из-за особенностей представления.

Логический тип (boolean)

Логический тип представляет собой самый простой из всех — он может принимать только два значения: true (истина) и false (ложь). Этот тип лежит в основе условной логики и управления потоком выполнения программы. Выражения, результатом которых является логическое значение, используются в конструкциях if, while, repeat и других управляющих структурах.

Над логическими значениями определены логические операции: конъюнкция (and), дизъюнкция (or), исключающее ИЛИ (xor) и отрицание (not). Эти операции позволяют строить сложные условия и комбинировать результаты нескольких проверок. Логический тип также может участвовать в арифметических выражениях в некоторых реализациях, где false интерпретируется как 0, а true — как 1, хотя такая практика не рекомендуется, так как снижает читаемость кода.

Символьный тип (char)

Символьный тип служит для хранения одного символа из кодовой таблицы, обычно ASCII или расширенной локальной кодировки. Каждый символ представлен своим числовым кодом, что позволяет выполнять преобразования между символами и целыми числами с помощью функций ord (получение кода символа) и chr (получение символа по коду).

Над символами можно выполнять операции сравнения: например, 'A' < 'B' вернёт true, так как код символа 'A' меньше кода 'B'. Это свойство активно используется при сортировке строк, анализе текста и проверке принадлежности символа к определённому классу (цифры, буквы, знаки препинания). Символы являются строительными блоками для строк, которые в Pascal реализуются как массивы символов или специальные строковые типы в зависимости от диалекта.


Составные типы данных

Составные типы данных в Pascal формируются из других типов — простых или также составных. Они предоставляют механизмы для группировки данных, упорядоченного хранения информации и создания абстракций, близких к предметной области задачи. К основным составным типам относятся массивы, записи, множества и файлы. Каждый из них решает свою задачу организации данных и обладает уникальными свойствами.

Массив (array)

Массив — это упорядоченная совокупность элементов одного и того же типа, доступ к которым осуществляется по индексу. Индекс может быть любого порядкового типа: целым числом, символом, перечислимым значением или диапазоном. Наиболее распространённый случай — массив с целочисленными индексами, начинающимися с нуля или единицы.

Объявление массива включает указание типа индекса и типа элементов. Например, array[1..10] of integer задаёт массив из десяти целых чисел с индексами от 1 до 10. Массивы могут быть многомерными: array[1..5, 1..5] of real описывает таблицу из 25 вещественных чисел, организованную как матрица 5×5.

Массивы обеспечивают быстрый прямой доступ к любому элементу по индексу, что делает их эффективными для задач, требующих частого чтения и записи по известному положению. Они широко применяются в численных методах, обработке сигналов, представлении сеток, таблиц и последовательностей. Однако размер массива фиксируется на этапе компиляции в классическом Pascal, что ограничивает гибкость при работе с данными переменного объёма. В современных диалектах, таких как Delphi или Free Pascal, существуют динамические массивы, размер которых можно изменять во время выполнения.

Запись (record)

Запись — это структура, объединяющая несколько полей, каждое из которых может иметь собственный тип. Запись позволяет моделировать сложные объекты, состоящие из разнородных компонентов. Например, запись может представлять информацию о человеке: имя (строка), возраст (целое число), рост (вещественное число), пол (перечислимый тип).

Поля записи обращаются через точку: если переменная person имеет тип записи с полем age, то person.age даёт доступ к значению возраста. Записи могут содержать другие записи, массивы, множества — любые допустимые типы, что позволяет строить иерархические структуры данных. В отличие от массива, где все элементы однотипны и идентифицируются индексом, в записи каждый компонент имеет уникальное имя и смысл.

Записи особенно полезны при проектировании программ, где требуется передавать связанные данные как единый блок. Они лежат в основе более сложных абстракций, таких как объекты в объектно-ориентированных расширениях Pascal (например, в Object Pascal). Даже без ООП записи остаются мощным инструментом для структурирования информации.

Множество (set)

Множество в Pascal — это неупорядоченная коллекция значений одного порядкового типа, где каждое значение либо присутствует, либо отсутствует. Множества реализованы как битовые маски: каждый возможный элемент базового типа соответствует одному биту, который установлен в 1, если элемент входит в множество, и в 0 — если не входит. Из-за этого ограничения базовый тип множества должен иметь не более 256 возможных значений (в большинстве реализаций), что делает множества подходящими для символов, малых целых диапазонов или перечислений.

Над множествами определены операции теории множеств: объединение (+), пересечение (*), разность (-), а также проверка принадлежности (in). Например, выражение 'a' in vowels проверяет, является ли символ 'a' гласной буквой, если vowels — множество, содержащее гласные. Множества обеспечивают чрезвычайно компактное и быстрое представление наборов флагов, категорий или состояний. Они часто используются в парсерах, конечных автоматах, системах прав доступа и других задачах, где требуется проверка наличия элемента в группе.

Файл (file)

Файл в Pascal представляет собой последовательность компонентов одного типа, хранящуюся во внешней памяти. Файлы позволяют сохранять данные между запусками программы, обмениваться информацией с другими системами или обрабатывать большие объёмы данных, не помещающиеся в оперативной памяти. Тип файла определяется типом его компонентов: file of integer, file of char, file of record и так далее.

Работа с файлами включает открытие, чтение, запись и закрытие. Pascal предоставляет процедурные средства: assign, reset, rewrite, read, write, close. Файлы обрабатываются последовательно: чтобы добраться до определённого элемента, нужно прочитать все предшествующие. Некоторые реализации поддерживают прямой доступ к компонентам по номеру, но это выходит за рамки стандарта.

Текстовые файлы являются особым случаем: они объявляются как text и содержат последовательность строк, разделённых специальными символами окончания строки. Для них доступны функции readln и writeln, которые автоматически управляют переходами между строками. Файлы играют важную роль в долговременном хранении данных, логировании, импорте и экспорте информации.


Пользовательские скалярные типы

Pascal предоставляет мощные средства для определения собственных простых типов на основе уже существующих. Это позволяет повысить читаемость кода, усилить контроль со стороны компилятора и выразить семантику задачи непосредственно в структуре программы. К таким типам относятся перечислимые типы и типы-диапазоны.

Перечислимые типы

Перечислимый тип задаёт конечный упорядоченный набор именованных значений. Программист явно перечисляет все допустимые константы типа. Например, можно определить тип color = (red, green, blue) или week_day = (monday, tuesday, wednesday, thursday, friday, saturday, sunday). Каждое значение получает внутренний целочисленный код, начиная с нуля: red соответствует 0, green — 1, blue — 2 и так далее.

Перечислимые типы являются порядковыми: над ними работают функции ord, succ (следующий элемент) и pred (предыдущий элемент). Это позволяет использовать их в циклах и как индексы массивов. Главное преимущество перечислений — повышение выразительности программы. Вместо магических чисел вроде status = 2 появляется понятное status = completed. Компилятор гарантирует, что переменная такого типа никогда не примет значение вне заданного списка, что предотвращает ошибки.

Перечислимые типы особенно полезны при моделировании состояний, категорий, режимов работы, команд меню и любых других ситуаций, где возможен ограниченный набор вариантов. Они органично сочетаются с оператором выбора case, обеспечивая полноту обработки всех случаев при хорошей практике программирования.

Типы-диапазоны

Тип-диапазон ограничивает множество значений базового порядкового типа подмножеством, задаваемым нижней и верхней границей. Синтаксис: 1..100, 'A'..'Z', monday..friday. Диапазон наследует все свойства своего базового типа, но сужает допустимые значения. Например, переменная, объявленная как score: 0..100, может хранить только целые числа от 0 до 100 включительно.

Использование диапазонов укрепляет защиту от ошибок. Если попытаться присвоить такой переменной значение 150, в режиме проверки диапазонов (включённом по умолчанию во многих реализациях при отладке) будет сгенерировано исключение времени выполнения. Это помогает выявлять логические ошибки на ранних этапах тестирования. Диапазоны также служат естественным способом задания границ массивов: array[1..12] of string — идеальное представление для названий месяцев.

Диапазоны могут быть основаны на любом порядковом типе: целых числах, символах, перечислениях. Они позволяют точно выразить контракт переменной: «эта величина всегда лежит в таких-то пределах». Это делает программу более самодокументированной и устойчивой к некорректным данным.


Указатели

Указатель в Pascal — это переменная, хранящая адрес другой переменной в памяти. Указатели предоставляют механизм для динамического распределения памяти и построения связных структур данных, таких как списки, деревья и графы. Объявление указателя включает указание типа данных, на которые он может ссылаться: ^integer, ^record_type.

Выделение памяти осуществляется процедурой new, освобождение — процедурой dispose. После вызова new(p) переменная p указывает на блок памяти, достаточный для хранения значения указанного типа. Доступ к значению по указателю осуществляется с помощью разыменования: p^ := 42.

Система указателей в Pascal проектировалась с акцентом на безопасность. В отличие от языков вроде C, где указатель может быть преобразован в целое число и наоборот, арифметика указателей ограничена или отсутствует, а неинициализированные указатели не могут использоваться без явного выделения памяти. Это снижает риск повреждения памяти, случайной записи в чужие области данных или использования «висячих» ссылок.

Несмотря на эти меры, работа с указателями требует дисциплины: каждое выделение должно иметь соответствующее освобождение, а использование указателя после dispose ведёт к неопределённому поведению. Тем не менее, в рамках учебных и средних по сложности проектов указатели в Pascal остаются управляемым и достаточно безопасным инструментом для реализации динамических структур.